print('=' * 60)

#. 加载库文件
#加载飞桨和相关类库
import paddle
import paddle.fluid as fluid
from paddle.fluid.dygraph.nn import Linear
import numpy as np
import os
from PIL import Image

#. 数据处理> 封装函数
#> 通过飞桨api 在线下载训练数据集
trainset = paddle.dataset.mnist.train()
#> 包装为数据读取器 train_reader，每次读取的数据数量设置为8
#@ paddle.batch函数将MNIST数据集拆分成多个批次
train_reader = paddle.batch(trainset, batch_size=8)

#> 以迭代的形式读取第一个批次的数据
for batch_id, data in enumerate(train_reader()):
    #? 获得图像数据，并转为float32类型的数组,不清楚知道为什么是float32类型
    img_data = np.array([x[0] for x in data]).astype('float32')
    #? 获得图像标签数据，并转为float32类型的数组,疑问同上
    label_data = np.array([x[1] for x in data]).astype('float32')
    #> 打印数据
    #@ numpy.ndarray.shape 是数据嵌套层次的说明,这里 img_data.shape 内有8个数组,每个数组784个数据
    """
        numpy.ndarray.shape 在笔记预置中有更详细解释
        img_data[0] 正常数组获得第一个数组中的数据
    """
    print(type(img_data))  
    print("图像数据形状和对应数据为:", img_data.shape, img_data[0])
    print("图像标签形状和对应数据为:", label_data.shape, label_data[0])
    break
# > format是格式化字符串将()内的内容替换到{}位置上去
print("\n打印第一个batch的第一个图像，对应标签数字为{}".format(label_data[0]))

#> 显示第一batch的第一个图像
# @ matplotlib.pyplot是一个python的2D绘图库
import matplotlib.pyplot as plt
#? 反归一化每个数据矩阵的内容的数据+1并*127.5 ,所以+1 和*127.5是归一化时知道的数据
img = np.array(img_data[0]+1)*127.5
# ? np.reshape更改数组的形状(矩阵形状?) 28*28 既img内28个数组,没个数组28个数据 .然后在转换数据的格式为 uint8是 无符号整数也会是正整数形式
img = np.reshape(img, [28, 28]).astype(np.uint8)

# > 设置plt参数 ,最后转换数组img为图像
# 图像窗口名称
# plt.figure("Image") 
# # 显示img数据
# plt.imshow(img)
# # 关掉坐标轴为 off
# plt.axis('on')
# #  设置图像题目
# plt.title('image') 
# #  显示
# plt.show()



#.使用房间预测的模型
# > 模型设计之网络结构
#@ fluid.dygraph.Layer是飞桨api: 基于OOD是实现的动图层.内置多个方法函数,在训练配置会使用到,但这里没有声明.
class MNIST(fluid.dygraph.Layer):
    def __init__(self):
        super(MNIST, self).__init__()
        
        #> 定义一层全连接层，input输入784的数据量, 输出维度是1(784放入一个数组中)，激活函数为None，即不使用激活函数
        #@ Linear 线性函数
        self.fc = Linear(input_dim=784, output_dim=1, act=None)
        
    #> forward函数中将网络串联:  定义网络结构的前向计算过程
    #@ self MNIST自己
    #@ inputs 输入数据
    def forward(self, inputs):
       
        #> 模型自己,通过的fc方式将输入的数据,转换成一维数据
        outputs = self.fc(inputs)
       
        #> 返回转换成一维的数据
        return outputs

#. 训练配置
#> 定义飞桨动态图工作环境
with fluid.dygraph.guard():
    #> 声明设计好的模型
    model = MNIST()

    #> 启动训练模式
    #@ train()是fluid.dygraph.Layer内置方法 :将此层及其所有子层设置为训练模式
    model.train()

    #> 定义数据读取函数，数据读取batch_size设置为16
    #@  paddle.dataset.mnist.train()是飞桨集成好的训练数据集,
    #@   paddle.batch,将获得的数据, 分为16个数组做为第一层数据
    train_loader = paddle.batch(paddle.dataset.mnist.train(), batch_size=16)
    
    #> 定义优化器，使用随机梯度下降SGD优化器，学习率设置为0.001
    #! 梯度下降跳过去了,需要回头在去看看 
    optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.001, 
    
    #> 回一个由当前层及其子层的所有参数组成的列表
    #@ parameters : 返回一个由当前层及其子层的所有参数组成的列表。
    parameter_list=model.parameters())

#. 训练过程
#> 通过with语句创建一个调整的运行环境的机会


with fluid.dygraph.guard():
    #> 训练配置的代码一致,这里不再填写注释
    model = MNIST()
    model.train()
    train_loader = paddle.batch(paddle.dataset.mnist.train(), batch_size=16)
    optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.001, parameter_list=model.parameters())
    
    #> 设定商量值, 一般代码中不会写魔术数字.数字均有常量来代替并注释说明 
    EPOCH_NUM = 10
    #> 循环 EPOCH_NUM 常量的次数 = 10次
    for epoch_id in range(EPOCH_NUM):
        #> train_loader()获得了16*N的数据假如到第二层循环中
        for batch_id, data in enumerate(train_loader()):
            #> 准备数据，格式需要转换成符合框架要求
            image_data = np.array([x[0] for x in data]).astype('float32')
            label_data = np.array([x[1] for x in data]).astype('float32').reshape(-1, 1)
            #> 将数据转为飞桨动态图格式
            image = fluid.dygraph.to_variable(image_data)
            label = fluid.dygraph.to_variable(label_data)
            
            #> 前向计算的过程
            predict = model(image)
            
            #>计算损失，取一个批次样本损失的平均值
            loss = fluid.layers.square_error_cost(predict, label)
            avg_loss = fluid.layers.mean(loss)
            
            #> 每训练了1000批次的数据，打印下当前Loss的情况
            if batch_id !=0 and batch_id  % 1000 == 0:
                print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))
            
            #> 后向传播，更新参数的过程
            avg_loss.backward()
            optimizer.minimize(avg_loss)
            model.clear_gradients()

    #. 保存模型
    fluid.save_dygraph(model.state_dict(), 'mnist')

